#include "vtkSmartPointer.h"

#include "vtkActor.h"
#include "vtkBrokenLineWidget.h"
#include "vtkCamera.h"
#include "vtkCommand.h"
#include "vtkDataSetMapper.h"
#include "vtkExtractSelection.h"
#include "vtkInformation.h"
#include "vtkLinearSelector.h"
#include "vtkMultiBlockDataSet.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper.h"
#include "vtkProgrammableFilter.h"
#include "vtkProperty.h"
#include "vtkRegressionTestImage.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkTextActor.h"
#include "vtkTextProperty.h"
#include "vtkUnstructuredGrid.h"
#include "vtkUnstructuredGridReader.h"

#include "vtkTestUtilities.h"

#include <sstream>

// Callback for the broken line widget interaction
class vtkBLWCallback : public vtkCommand
{
public:
  static vtkBLWCallback* New() { return new vtkBLWCallback; }
  void Execute(vtkObject* caller, unsigned long, void*) override
  {
    // Retrieve polydata line
    vtkBrokenLineWidget* line = reinterpret_cast<vtkBrokenLineWidget*>(caller);
    line->GetPolyData(Poly);

    // Update linear extractor with current points
    this->Selector->SetPoints(Poly->GetPoints());

    // Update selection from mesh
    this->Extractor->Update();
    vtkMultiBlockDataSet* outMB = vtkMultiBlockDataSet::SafeDownCast(this->Extractor->GetOutput());
    vtkUnstructuredGrid* selection = vtkUnstructuredGrid::SafeDownCast(outMB->GetBlock(0));
    this->Mapper->SetInputData(selection);

    // Update cardinality of selection
    std::ostringstream txt;
    txt << "Number of selected elements: " << (selection ? selection->GetNumberOfCells() : 0);
    this->Text->SetInput(txt.str().c_str());
  }
  vtkBLWCallback()
    : Poly(nullptr)
    , Selector(nullptr)
    , Extractor(nullptr)
    , Mapper(nullptr)
    , Text(nullptr)
  {
  }
  vtkPolyData* Poly;
  vtkLinearSelector* Selector;
  vtkExtractSelection* Extractor;
  vtkDataSetMapper* Mapper;
  vtkTextActor* Text;
};

int TestBrokenLineWidget(int argc, char* argv[])
{
  // Create render window and interactor
  vtkSmartPointer<vtkRenderWindow> win = vtkSmartPointer<vtkRenderWindow>::New();
  win->SetMultiSamples(0);
  win->SetSize(600, 300);
  vtkSmartPointer<vtkRenderWindowInteractor> iren =
    vtkSmartPointer<vtkRenderWindowInteractor>::New();
  iren->SetRenderWindow(win);
  iren->Initialize();

  // Create 2 viewports in window
  vtkSmartPointer<vtkRenderer> ren1 = vtkSmartPointer<vtkRenderer>::New();
  ren1->SetBackground(.4, .4, .4);
  ren1->SetBackground2(.8, .8, .8);
  ren1->GradientBackgroundOn();
  ren1->SetViewport(0., 0., .5, 1.);
  win->AddRenderer(ren1);
  vtkSmartPointer<vtkRenderer> ren2 = vtkSmartPointer<vtkRenderer>::New();
  ren2->SetBackground(1., 1., 1.);
  ren2->SetViewport(.5, 0., 1., 1.);
  win->AddRenderer(ren2);

  // Create a good view angle
  vtkCamera* camera = ren1->GetActiveCamera();
  camera->SetFocalPoint(.12, 0., 0.);
  camera->SetPosition(.38, .3, .15);
  camera->SetViewUp(0., 0., 1.);
  ren2->SetActiveCamera(camera);

  // Read 3D unstructured input mesh
  char* fileName = vtkTestUtilities::ExpandDataFileName(argc, argv, "Data/AngularSector.vtk");
  vtkSmartPointer<vtkUnstructuredGridReader> reader =
    vtkSmartPointer<vtkUnstructuredGridReader>::New();
  reader->SetFileName(fileName);
  delete[] fileName;
  reader->Update();

  // Create mesh actor to be rendered in viewport 1
  vtkSmartPointer<vtkDataSetMapper> meshMapper = vtkSmartPointer<vtkDataSetMapper>::New();
  meshMapper->SetInputConnection(reader->GetOutputPort());
  vtkSmartPointer<vtkActor> meshActor = vtkSmartPointer<vtkActor>::New();
  meshActor->SetMapper(meshMapper);
  meshActor->GetProperty()->SetColor(.23, .37, .17);
  meshActor->GetProperty()->SetRepresentationToWireframe();
  ren1->AddActor(meshActor);

  // Create multi-block mesh for linear extractor
  reader->Update();
  vtkUnstructuredGrid* mesh = reader->GetOutput();
  vtkSmartPointer<vtkMultiBlockDataSet> meshMB = vtkSmartPointer<vtkMultiBlockDataSet>::New();
  meshMB->SetNumberOfBlocks(1);
  meshMB->GetMetaData(static_cast<unsigned>(0))->Set(vtkCompositeDataSet::NAME(), "Mesh");
  meshMB->SetBlock(0, mesh);

  // Create broken line widget, attach it to input mesh
  vtkSmartPointer<vtkBrokenLineWidget> line = vtkSmartPointer<vtkBrokenLineWidget>::New();
  line->SetInteractor(iren);
  line->SetInputData(mesh);
  line->SetPriority(1.);
  line->KeyPressActivationOff();
  line->PlaceWidget();
  line->ProjectToPlaneOff();
  line->On();
  line->SetHandleSizeFactor(1.2);

  // Create list of points to define broken line
  vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
  points->InsertNextPoint(.23, .0, .0);
  points->InsertNextPoint(.0, .0, .0);
  points->InsertNextPoint(.23, .04, .04);
  line->InitializeHandles(points);

  // Extract polygonal line and then its points
  vtkSmartPointer<vtkPolyData> linePD = vtkSmartPointer<vtkPolyData>::New();
  line->GetPolyData(linePD);
  vtkSmartPointer<vtkPolyDataMapper> lineMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
  lineMapper->SetInputData(linePD);
  vtkSmartPointer<vtkActor> lineActor = vtkSmartPointer<vtkActor>::New();
  lineActor->SetMapper(lineMapper);
  lineActor->GetProperty()->SetColor(1., 0., 0.);
  lineActor->GetProperty()->SetLineWidth(2.);
  ren2->AddActor(lineActor);

  // Create selection along broken line defined by list of points
  vtkSmartPointer<vtkLinearSelector> selector = vtkSmartPointer<vtkLinearSelector>::New();
  selector->SetInputData(meshMB);
  selector->SetPoints(points);
  selector->IncludeVerticesOff();
  selector->SetVertexEliminationTolerance(1.e-12);

  // Extract selection from mesh
  vtkSmartPointer<vtkExtractSelection> extractor = vtkSmartPointer<vtkExtractSelection>::New();
  extractor->SetInputData(0, meshMB);
  extractor->SetInputConnection(1, selector->GetOutputPort());
  extractor->Update();
  vtkMultiBlockDataSet* outMB = vtkMultiBlockDataSet::SafeDownCast(extractor->GetOutput());
  vtkUnstructuredGrid* selection = vtkUnstructuredGrid::SafeDownCast(outMB->GetBlock(0));

  // Create selection actor
  vtkSmartPointer<vtkDataSetMapper> selMapper = vtkSmartPointer<vtkDataSetMapper>::New();
  selMapper->SetInputData(selection);
  vtkSmartPointer<vtkActor> selActor = vtkSmartPointer<vtkActor>::New();
  selActor->SetMapper(selMapper);
  selActor->GetProperty()->SetColor(0., 0., 0.);
  selActor->GetProperty()->SetRepresentationToWireframe();
  ren2->AddActor(selActor);

  // Annotate with number of elements
  vtkSmartPointer<vtkTextActor> txtActor = vtkSmartPointer<vtkTextActor>::New();
  std::ostringstream txt;
  txt << "Number of selected elements: " << (selection ? selection->GetNumberOfCells() : 0);
  txtActor->SetInput(txt.str().c_str());
  txtActor->SetTextScaleModeToViewport();
  txtActor->SetNonLinearFontScale(.2, 18);
  txtActor->GetTextProperty()->SetColor(0., 0., 1.);
  txtActor->GetTextProperty()->SetFontSize(18);
  ren2->AddActor(txtActor);

  // Invoke callback on polygonal line to interactively select elements
  vtkSmartPointer<vtkBLWCallback> cb = vtkSmartPointer<vtkBLWCallback>::New();
  cb->Poly = linePD;
  cb->Selector = selector;
  cb->Extractor = extractor;
  cb->Mapper = selMapper;
  cb->Text = txtActor;
  line->AddObserver(vtkCommand::InteractionEvent, cb);

  // Render and test
  win->Render();
  int retVal = vtkRegressionTestImage(win);
  if (retVal == vtkRegressionTester::DO_INTERACTOR)
  {
    iren->Start();
  }

  return !retVal;
}
